﻿using System;
using System.Collections.Generic;
using CoreLibrary.Cache;
using CoreLibrary.Service;
using CoreLibrary.Sql.Linq;
using IBBI.DataBase;
using IBBI.DataBase.Impl;
using NLog;
using Ninject;
using Web.Config;

namespace Web {

    public class Services {

        private static readonly List<IService> All = new List<IService>();
        private volatile static StandardKernel _kernel;

        private static void InitServices() {
            Failover(InitConecctionProvider);
            Failover(InitCache);
            
            Failover(InitUserService);
            Failover(InitCategoryService);
        }

        private static IUserService InitUserService () {
            return InjectAndStart<IUserService, UserService>(
                new UserService {
                    Connection = Locate<ILinqConnectionProvider>(),
                    Cache = Locate<ICache>()
                }
            );
        }

        private static ICategoryService InitCategoryService() {
            return InjectAndStart<ICategoryService, CategoryService>(
                new CategoryService {
                    Connection = Locate<ILinqConnectionProvider>(),
                    Cache = Locate<ICache>()
                }
            );
        }

        private static ILinqConnectionProvider InitConecctionProvider() {
            return InjectAndStart<ILinqConnectionProvider, LinqConnectionProvider>(
                   new LinqConnectionProvider {
                       ConnectionName   = "DatabaseConnectionString",
                       ConnectionString = AppSettings.ConnectionStrings
                   }
            );
        }

        private static ICache InitCache() {
            return InjectAndStart<ICache, EntLibCache>(new EntLibCache());
        }

        public static T Locate<T> () where T : class {
            var service = GetKernel().Get<T>();
            if (service == null) {
                throw new ArgumentException("Not found instance of " + typeof(T).Name);
            }
            return service;
        }

        public static T Locate<T> (string name) where T : class {
            var service = GetKernel().Get<T>(name);
            if (service == null) {
                throw new ArgumentException("Not found instance of " + typeof(T).Name + " named '" + name + "'");
            }
            return service;
        }

        private static T Failover<T> (Func<T> func) where T : class, IService {
            try {
                return func();
            } catch (Exception e) {
                LogManager.GetCurrentClassLogger().ErrorException("Unexpected failure: " + e.Message, e);
                return null;
            }
        }

        private static TFrom InjectAndStart<TFrom, TTo> (TTo service)
            where TTo : TFrom
            where TFrom : IService {
            var serviceImpl = Inject<TFrom, TTo>(service);
            if (serviceImpl.ServiceState.Code == ServiceState.Initialized) {
                serviceImpl.Start();
            }
            return serviceImpl;
        }

        private static TFrom Inject<TFrom, TTo> (TTo service)
            where TTo : TFrom
            where TFrom : IService {
            All.Add(service);
            service.Name = service.GetType().Name;
            service.Init();
            if (service.ServiceState.Code == ServiceState.Initialized) {
                _kernel.Bind<TFrom>()
                       .ToConstant(service)
                       .InSingletonScope();
            }
            return service;
        }

        private static TFrom InjectWithName<TFrom, TTo> (string name, TTo service)
            where TTo : TFrom
            where TFrom : IService {
            All.Add(service);
            service.Name = service.GetType().Name;
            service.Init();
            if (service.ServiceState.Code == ServiceState.Initialized) {
                _kernel.Bind<TFrom>()
                       .ToConstant(service)
                       .InSingletonScope()
                       .Named(name);
            }
            return service;
        }

        private static StandardKernel GetKernel () {
            if (_kernel == null) {
                lock (All) {
                    if (_kernel == null) {
                        try {
                            _kernel = new StandardKernel();
                            InitServices();
                            LogManager.GetCurrentClassLogger().Info("Services Started");
                        } catch (Exception error) {
                            LogManager.GetCurrentClassLogger().ErrorException("Services failed to start: " + error.Message, error);
                        }
                    }
                }
            }
            return _kernel;
        }

    }

}